
#ifndef __RSP_S_OBJECT__HEADER_FILE__
#define __RSP_S_OBJECT__HEADER_FILE__

#include "coid/comm/binstream/binstreambuf.h"
#include "coid/comm/str.h"
#include "s_update.h"


using namespace coid;


////////////////////////////////////////////////////////////////////////////////
///Server side rsp_s_object - state manager for one replicated object
class rsp_s_object_state : public rsp_state
{
protected:

    uint  _introduced;                  ///< state id when the object was connected

    ///Command buffer holds command packets for each trailing frame
    rsp_commandbuffer _cmdbuf;

    public:
    static rsp_s_object_state* get_from_ptr( rsp_base* p );

    rsp_s_object_state()
    {
        reset();
    }

    void set_object( rsp_base* obj, uint oid )
    {
        reset();
        obj->state = this;
        rsp_state::base = obj;
        rsp_state::oid = oid;
        base->touch();
    }

    void reset()
    {
        if(base)
            base->touch();
        _cmdbuf.reset();
        _introduced = RSPS_SETTINGS.active_frame();
    }

    uint oid() const                    { return rsp_state::oid; }
    rsp_base* object_ptr() const        { return rsp_state::base; }

    token class_name() const            { return base->class_name(); }

    void stream_out( binstream& bin )   { return base->stream_out(bin); }


    binstream& write_commands()         { _cmdbuf.prepare_packet_to_write(RSP_SETTINGS._ntrailing,true); return _cmdbuf; }

    ///Write part of object's command buffer
    bool write_diff( rsp_s_update& upd )
    {
        binstream& bin = *upd._bin;

        //incremental update
        //we have to send whole waiting queue normally, unless it's known that the client's
        // acknowledged state has been sent with some of them already
        //TODO: remember the number of waiting frames sent for most likely coming ack state
        uint tsz;
        uint pos = _cmdbuf.get_packet_lengths( upd._ntrailskip, UMAX, RSPS_SETTINGS._wrk_cmdlengths, &tsz );
        uint nst = RSPS_SETTINGS._wrk_cmdlengths.size() - pos;

        if(!nst)
            return false;

        //cmdlag+1
        //DASSERT( pos+nst <= RSP_SETTINGS._ntrailing - upd._ntrailskip + RSP_SETTINGS._nleading + 2 );

        //inc.update starts with number of empty frames from the last acked state followed by the number
        // of transferred states
        bin << (uchar)pos;
        bin << (uchar)nst;

        //write command lengths
        bin.xwrite_raw( RSPS_SETTINGS._wrk_cmdlengths.ptr()+pos, nst * sizeof(ushort) );

        circularstreambuf::pckdata a,b;
        _cmdbuf.get_packet_data( upd._ntrailskip + pos, a, b, nst );
        bin.xwrite_raw( a.ptr, a.size );
        if( b.size )  bin.xwrite_raw( b.ptr, b.size );

        //DASSERT( cdcd_memcheck( a.ptr, a.ptr+a.size, b.ptr, b.ptr+b.size ) );
        DASSERT( a.size + b.size == tsz );

        return true;
    }

    ///Write part of object's command buffer
    bool write_diff2( rsp_s_update& upd )
    {
        binstream& bin = *upd._bin;

        //incremental update
        //we have to send whole waiting queue normally, unless it's known that the client's
        // acknowledged state has been sent with some of them already
        //TODO: remember the number of waiting frames sent for most likely coming ack state
        uint tsz;
        uint pos = _cmdbuf.get_packet_lengths( upd._ntrailskip, UMAX, RSPS_SETTINGS._wrk_cmdlengths, &tsz );
        uint nst = RSPS_SETTINGS._wrk_cmdlengths.size() - pos;

        if(!nst)
            return false;

        //cmdlag+1
        //DASSERT( pos+nst <= RSP_SETTINGS._ntrailing - upd._ntrailskip + RSP_SETTINGS._nleading + 2 );

        //inc.update starts with number of empty frames from the last acked state followed by the number
        // of transferred states
        bin << (ushort)oid();
        bin << (uchar)pos;
        bin << (uchar)nst;

        //write command lengths
        bin.xwrite_raw( RSPS_SETTINGS._wrk_cmdlengths.ptr()+pos, nst * sizeof(ushort) );

        circularstreambuf::pckdata a,b;
        _cmdbuf.get_packet_data( upd._ntrailskip + pos, a, b, nst );
        bin.xwrite_raw( a.ptr, a.size );
        if( b.size )  bin.xwrite_raw( b.ptr, b.size );

        //DASSERT( cdcd_memcheck( a.ptr, a.ptr+a.size, b.ptr, b.ptr+b.size ) );
        DASSERT( a.size + b.size == tsz );

        return true;
    }

    ///Read client commands
    ///@return nonzero if successful
    int read_cmd( uchar nst, uint ackstateid, binstream& bin )
    {
        uchar rep;
        bin >> rep;

        RSPS_SETTINGS._wrk_cmdlengths.need_new(nst);
        bin.xread_raw( RSPS_SETTINGS._wrk_cmdlengths.ptr(), RSPS_SETTINGS._wrk_cmdlengths.byte_size() );

        uint s = RSP_SETTINGS.active_frame() - ackstateid;
        uint d = 0;
        uint i;
        for( i=0; i<s && nst>0; ++i ) {
            if( i >= rep ) {
                bin.xread_raw_scrap( RSPS_SETTINGS._wrk_cmdlengths[d++] );
                --nst;
            }
        }

        for( ; nst>0; ++i )
        {
            if( i >= rep )
            {
                //DASSERT( i-s <= RSP_SETTINGS._nleading+1 );
                uints bs = RSPS_SETTINGS._wrk_cmdlengths[d++];
                if(bs)
                {
                    _cmdbuf.prepare_packet_to_write( RSP_SETTINGS._ntrailing+i-s, false );

                    _cmdbuf.write_packet( bs, bin );
                    _cmdbuf.flush();
                
                    LOG(2) { *TLOG << "recv cmd: " << timefmt(ackstateid+i-RSP_SETTINGS._nleading)
                        << " (" << i << "/" << s << ")" << BINSTREAM_FLUSH; }
                }

                --nst;
            }
        }

        bin.acknowledge();
        return -1;
    }



    void frame()
    {
        //DASSERT( _cmdbuf.packets() <= 1 + RSP_SETTINGS._ntrailing );

        //eat the oldest packet
        _cmdbuf.acknowledge(true);

        //T should have a 'void frame()' and 'void command( uchar cmd, binstream& )' methods defined
        if( _cmdbuf.prepare_packet_to_read( RSP_SETTINGS._ntrailing - 1 ) )
        {
            while( _cmdbuf.bytes_to_read() )
            {
                uchar cd;
                _cmdbuf >> cd;
                base->command( cd, _cmdbuf );

                LOG(2) { *TLOG << "command: " << cd << " frame: "
                    << timefmt(RSPS_SETTINGS.active_frame()-RSP_SETTINGS._nleading) << BINSTREAM_FLUSH; }
            }
        }

        base->frame();
    }

    void frame_end()
    {
        //save the full state if the object was touched by server
        if( dirty() )
        {
            //WARNING: discards trailing commands, no big deal though
            _cmdbuf.prepare_packet_to_write( RSP_SETTINGS._ntrailing - 1, true );
            _cmdbuf << (uchar)0xff;
            base->stream_out( _cmdbuf );
            clean();//oid &= ~rsp_state::fTOUCHED;
        }

        _cmdbuf.flush();
    }

protected:
/*
    binstreambuf& get_active_stream()
    {
        return _execbuf[ RSPS_SETTINGS._active ];
    }

    binstreambuf& get_execbuf( uint i )
    {
        i += RSPS_SETTINGS._active;
        i &= RSP_SETTINGS._nleading - 1;
        return _execbuf[i];
    }
*/
};




#endif //__RSP_S_OBJECT__HEADER_FILE__
